<!--
 * @Author: zhangyonggui
 * @Date: 2023-10-10 10:57:06
 * @LastEditors: zhangyonggui
 * @LastEditTime: 2024-01-17 17:08:12
 * @Descripttion: 攻击面分析
-->
<template>
  <div id="graphContainer" class="graph-wrap" ref="parentRoot">
    <GraphToolbar
      :graph="graph"
      :changeSize="changeSize"
      :parentRoot="parentRoot"
    ></GraphToolbar>

    <!-- g6 容器 -->
    <!-- 以防数据量过大,加载速度慢,引入 element ui 的 loading -->
    <div
      v-if="!lengthShow"
      id="container"
      v-loading="loading"
      element-loading-text="加载中..."
      element-loading-background="rgba(0, 0, 0, 0.8)"
      style="width: 100%; height: 300px"
    ></div>

    <noData v-else class="no-data"></noData>
  </div>
</template>
<script lang="ts" setup>
// 导入 G6
import G6 from '@antv/g6';
import GraphToolbar from './Toolbar.vue';
import {
  defineProps,
  defineEmits,
  onUnmounted,
  ref,
  onMounted,
  nextTick,
} from 'vue';
import { useStore } from 'vuex';
const emit = defineEmits(['g6Click', 'showEnlarge', 'g6Click']);
const store = useStore();
const props = defineProps({
  rowList: {
    type: Object,
    default: () => {
      return {};
    },
  },
  changeSize: {
    type: Function,
  },
});
const graphContainer = ref<any>(null);
const parentRoot = ref<any>(null);
const loading = ref(true);
const graph = ref<any>(null);
const g6Data = ref<any>({});
const g6Total = ref(0);
const lengthG6 = ref(0);
const lengthShow = ref(false);
const canvasWidth = ref(0);
const canvasHeight = ref(0);
onMounted(async () => {
  const graphContainer: any = document.getElementById('graphContainer');
  canvasWidth.value = graphContainer.clientWidth;
  canvasHeight.value = graphContainer.clientHeight;
  await getSurfaceAnalysis();
  window.addEventListener('resize', handleWindowResize);
});
onUnmounted(() => {
  window.removeEventListener('resize', handleWindowResize);
});

const getSurfaceAnalysis = async () => {
  const { code, data } = await store.dispatch(
    'detail/getSurfaceAnalysis',
    props.rowList,
  );
  if (Number(code) === 0) {
    g6Data.value = data.data;
    g6Total.value = data.total;
    loading.value = false;
    lengthG6.value = g6Data.value.children.length;
    if (g6Data.value.children.length > 0) {
      emit('g6Click', data.data.vulId, g6Total.value);
      render();
      lengthShow.value = false;
    } else {
      lengthShow.value = true;
      emit('showEnlarge', false);
    }
  }
};
// g6 配置
const render = async () => {
  await nextTick();
  // 文本超出隐藏
  // fittingString(字段, 最大长度, 字体大小)
  const fittingString = (str, maxWidth, fontSize) => {
    const ellipsis = '...';
    const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0];
    let currentWidth = 0;
    let res = str;
    const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
    str.split('').forEach((letter, i) => {
      if (currentWidth > maxWidth - ellipsisLength) return;
      if (pattern.test(letter)) {
        // Chinese charactors
        currentWidth += fontSize;
      } else {
        // get the width of single letter according to the fontSize
        currentWidth += G6.Util.getLetterWidth(letter, fontSize);
      }
      if (currentWidth > maxWidth - ellipsisLength) {
        res = `${str.substr(0, i)}${ellipsis}`;
      }
    });
    return res;
  };
  // todo 设置鼠标悬停显示详情操作
  const tooltip = new G6.Tooltip({
    offsetX: 10,
    offsetY: 10,
    getContent(e: any) {
      const outDiv = document.createElement('div');
      outDiv.style.width = '220px';
      outDiv.style.position = 'relative';
      outDiv.innerHTML = `
          <div>
          <p>${e.item.getModel().content}</p>
          </div>`;
      return outDiv;
    },
    itemTypes: ['node'],
  });
  // 自定义节点
  G6.registerNode(
    'tree-node',
    {
      drawShape: function drawShape(cfg: any, group: any) {
        const rect = group.addShape('rect', {
          attrs: {
            x: 0, // x 轴移动距离
            y: 0, // y 轴移动距离
            width: cfg.nood == 1 ? 170 : 190, // 宽
            height: 65, // 高
            // fill: '#666', // 背景色
            // stroke: '#666',// 边框色
          },
          name: 'big-rect-shape',
        });
        // 主节点
        if (cfg.depth == 0) {
          /* 盒子 */
          group.addShape('rect', {
            attrs: {
              fill: '#fff',
              x: 1,
              y: 5,
              width: 228,
              height: 52,
              radius: 4,
              cursor: 'pointer',
            },
            name: 'rect-shape',
          });
          /* 第一个图标 */
          group.addShape('image', {
            attrs: {
              x: 10,
              y: 18,
              height: 26,
              width: 26,
              img: require('@/static/images/retrieval/time.png'),
            },
            name: 'state-icon',
          });
          group.addShape('text', {
            attrs: {
              fill: '#8590B3',
              text: fittingString(cfg.content, 140, 14),
              x: 40,
              y: 33,
              fontSize: 14,
              textAlign: 'left',
              textBaseline: 'middle',
            },
            name: 'source-text-shape',
          });
        }
        // 节点2
        if (cfg.depth == 1) {
          group.addShape('rect', {
            attrs: {
              fill: ' #fff',
              x: 0,
              y: 5,
              width: 228,
              height: 52,
              radius: 4,
              cursor: 'pointer',
            },
            name: 'rect-shape',
          });
          /* 第一个图标 */
          group.addShape('image', {
            attrs: {
              x: 10,
              y: 16,
              height: 26,
              width: 26,
              img: require('@/static/images/retrieval/zise.png'),
            },
            name: 'state-icon',
          });
          /* 第一行字 */
          group.addShape('text', {
            attrs: {
              text: fittingString(cfg.content, 130, 12),
              x: 45,
              y: 30,
              fontSize: 14,
              textAlign: 'left',
              textBaseline: 'middle',
              fill: '#8590B3',
              cursor: 'pointer',
            },
            name: 'text-shape',
          });
        }
        // 第3个节点
        if (cfg.depth == 2) {
          /* 盒子 */
          group.addShape('rect', {
            attrs: {
              fill: '#fff',
              x: 145,
              y: 5,
              width: 100,
              height: 52,
              radius: 4,
            },
            name: 'rect-shape',
          });
          /* 第一个图标 */
          group.addShape('image', {
            attrs: {
              x: 155,
              y: 18,
              height: 26,
              width: 26,
              img: require('@/static/images/retrieval/heike.png'),
            },
            name: 'state-icon',
          });
          group.addShape('text', {
            attrs: {
              text: cfg.content,
              x: 190,
              y: 33,
              fontSize: 14,
              textAlign: 'left',
              textBaseline: 'middle',
              fill: '#8590B3',
            },
            name: 'text-shape',
          });
        }
        return rect;
      },
    },
    'single-node',
  );
  const container: any = document.getElementById('container'); // 获取容器id
  if (container) {
    const width = container.scrollWidth || 800;
    const height = container.scrollHeight || 300; // 容器宽高
    const graphs = new G6.TreeGraph({
      container,
      width,
      height,
      plugins: [tooltip], // 配置 Tooltip 插件
      // 设置画布的模式
      modes: {
        default: [
          'activate-relations',
          //   滚动
          // 'scroll-canvas'
          // // 拖拽画布；
          'drag-canvas',
          // // 缩放画布；
          'zoom-canvas',
          //  'drag-node'
        ],
      },

      // 默认状态下节点的配置，比如 type, size, color,会被写入的 data 覆盖
      defaultNode: {
        type: 'tree-node',
        // 指定边连入节点的连接点的位置（相对于该节点而言），可以为空
        anchorPoints: [
          [0, 0.5],
          [1, 0.5],
        ],
      },
      nodeStateStyles: {
        highlight: {
          // fill: "red",
        },
      },
      // 默认状态下边的配置
      defaultEdge: {
        // 指定边的类型，可以是内置边的类型名称，也可以是自定义边的名称。默认为 'line'
        type: 'cubic-horizontal',
        // 边的样式属性
        style: {
          // 边的颜色
          stroke: '#A3B1BF',
          // 开始箭头
          startArrow: {
            path: G6.Arrow.circle(2, 2), // 内置样式
            fill: '#A3B1BF', // 颜色
            d: 4, // 偏移距离
          },
        },
      },
      layout: {
        // 布局名称
        type: 'compactBox',
        direction: 'H',
        preventOverlap: true, // 防止节点重叠
        // 判断节点向左还是向右
        getSide: (d) => {
          // if (d.data.direction == '2') {
          //   return 'right';
          // }
          return 'left';
        },
        // 节点 id 的回调函数
        getId: function getId(d) {
          return d.id;
        },
        // 节点高度的回调函数
        getHeight: function getHeight() {
          return 40;
        },
        // 节点宽度的回调函数
        getWidth: function getWidth() {
          return 100;
        },
        // 节点纵向间距的回调函数
        getVGap: function getVGap() {
          return 20;
        },
        // 节点横向间距的回调函数
        getHGap: function getHGap() {
          return 110;
        },
      },
    });
    graph.value = graphs;
    // 初始化的图数据
    graphs.data(g6Data.value);
    // 根据提供的数据渲染视图。
    graphs.render();
    // 缩放视窗窗口到一个固定比例
    graphs.zoomTo(1);
    //  graph.fitView([0, 20]) // 自适应
    // 平移图到中心将对齐到画布中心，但不缩放
    graphs.fitCenter();
    graphs.on('node:click', (e: any) => {
      const model = e.item.getModel();
      const item = e.item;
      if (model.depth == 1) {
        emit('g6Click', model.vulId, g6Total.value);
        // eslint-disable-next-line no-undef
        graphs.setItemState(item, 'highlight', true);
      }
      if (model.depth == 0) {
        emit('g6Click', '', g6Total.value);
        // eslint-disable-next-line no-undef
        graphs.setItemState(item, 'highlight', true);
      }
    });
    loading.value = false;
  }
};
// 修改画布大小
const changeSize = async () => {
  const graphContainer: any = document.getElementById('graphContainer');
  if (graphContainer) {
    canvasWidth.value = graphContainer.clientWidth;
    canvasHeight.value = graphContainer.clientHeight;
    graph.value.changeSize(canvasWidth.value, canvasHeight.value);
  }
};

const handleWindowResize = () => {
  if (!graph.value || graph.value.get('destroyed')) return;
  changeSize();
};
</script>
<style lang="scss" scoped>
.graph-wrap {
  position: relative;
  width: 100%;
  height: 100%;
  background-color: $-white;
}

.no-data {
  margin-top: $-v-spacing-horizontal-base;
}
</style>
